package controllers

import java.io._
import java.net.URL
import com.google.inject.Provides
import com.hhandoko.play.pdf.PdfGenerator
import command.CommandUtils
import dao._

import javax.inject.Inject
import models.Tables.MissionRow
import org.apache.commons.io.FileUtils
import org.joda.time.DateTime
import org.jxls.common.Context
import org.jxls.util.JxlsHelper
import play.api.libs.json.Json
import play.api.mvc.{AbstractController, ControllerComponents, RequestHeader}
import tool.{DivData, FileTool, FormTool, ImageInfo, MyLogger, PdfGeneratorProvider, Tool}
import utils.Utils
import models.Tables._
import net.logstash.logback.marker.LogstashMarker
import org.apache.commons.lang3.StringUtils
import org.slf4j.MarkerFactory
import play.api
import play.api.{Logger, MarkerContext}

import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import implicits.Implicits._
import shared.Shared
import spdf._
import upickle.default._
import shared.pojo.Pojo.predict._

/** Created by yz on 2018/12/12
  */
class PredictController @Inject() (
    cc: ControllerComponents,
    formTool: FormTool,
    tool: Tool,
    missionDao: MissionDao,
    extraDataDao: ExtraDataDao,
    trashDao: TrashDao,
    pdfGen: PdfGeneratorProvider
) extends AbstractController(cc) {

  val logger = MyLogger(tool)(this.getClass())

  def batchPredictBefore = Action { implicit request =>
    Ok(views.html.user.batchPredict())
  }

  def predict = Action.async { implicit request =>
    val data = Tool.transformRq.transform(reader[PredictData])
    val myMessage = Tool.dataCheck(data).toMyMessage
    if (myMessage.valid) {
      val tmpDir = tool.createTempDirectory("tmpDir")
      val inputFile = new File(tmpDir, "input.txt")
      val lines = ArrayBuffer(
        ArrayBuffer(
          "SampleID",
          "Age",
          "AST",
          "ALT",
          "PLT",
          "Tyr",
          "TCA",
          "Phe",
          "Trp",
          "Val",
          "CA",
          "Met",
          "GCA",
          "GCDCA",
          "TCDCA"
        )
      )
      lines += ArrayBuffer(
        data.sampleId,
        data.age,
        data.ast,
        data.alt,
        data.plt,
        data.tyr,
        data.tca,
        data.phe,
        data.trp,
        data.valine,
        data.ca,
        data.met,
        data.gca,
        data.gcdca,
        data.tcdca
      )
      Utils.lines2File(inputFile, lines.map(_.mkString("\t")))
      Utils.txt2Xlsx(inputFile, new File(tmpDir, "input.xlsx"))
      FileUtils.copyFileToDirectory(new File(Utils.dataDir, "LiveForest_load.RData"), tmpDir)
      val command =
        s"""
           |dos2unix *
           |Rscript ${Utils.rPath.unixPath}/predict.R
         """.stripMargin
      val execCommand = CommandUtils.orderCallLinuxScript(tmpDir, shBuffer = List(command))
      if (execCommand.isSuccess) {
        val outFile = new File(tmpDir, "out.txt")
        val resultData = tool.getResultData(outFile)
        val score = Shared.calculateScore(resultData)
        val result = Shared.getResult(score)
        val time = new DateTime()
        val row = MissionRow(
          id = 0,
          userId = data.userId,
          age = data.age,
          ast = data.ast,
          alt = data.alt,
          plt = data.plt,
          tyr = data.tyr,
          tca = data.tca,
          phe = data.phe,
          trp = data.trp,
          valine = data.valine,
          ca = data.ca,
          met = data.met,
          gca = data.gca,
          gcdca = data.gcdca,
          tcdca = data.tcdca,
          score = score.toString(),
          result = result,
          endTime = time
        )
        missionDao.insertAndRetunId(row).flatMap { missionId =>
          val extraRow = tool
            .getEmptyExtraDataRow(missionId, data.userId)
            .copy(
              sampleId = data.sampleId,
              name = data.name,
              checkDate = Utils.dataTime2String(time)
            )
          val trashRow = TrashRow(missionId, data.userId, true)
          extraDataDao.insertOrUpdate(extraRow).zip(trashDao.insert(trashRow)).map { x =>
            val missionIdDir = tool.getMissionIdDirById(missionId, data.userId)
            val outFile = new File(tmpDir, "out.txt")
            FileUtils.copyFileToDirectory(outFile, missionIdDir)
            tool.deleteDirectory(tmpDir)
            val json = Json.obj("missionId" -> missionId)
            Ok(json)
          }

        }
      } else {
        tool.deleteDirectory(tmpDir)
        Utils.result2Future(Ok(Json.obj("valid" -> "false", "message" -> execCommand.getErrStr)))
      }
    } else {
      Utils.result2Future(Ok(Json.obj("valid" -> myMessage.valid, "message" -> myMessage.message)))
    }

  }

  def batchPredict = Action.async(parse.multipartFormData) { implicit request =>
    val tmpDir = tool.createTempDirectory("tmpDir")
    val inputFile = new File(tmpDir, "input.xlsx")
    val file = request.body.file("file").get
    file.ref.moveTo(inputFile, replace = true)
    val myMessage = FileTool.fileCheck(inputFile)
    if (myMessage.valid) {
      val userId = tool.getUserId
      val time = new DateTime()
      FileUtils.copyFileToDirectory(new File(Utils.dataDir, "LiveForest_load.RData"), tmpDir)
      val command =
        s"""
           |Rscript ${Utils.rPath.unixPath}/predict.R
         """.stripMargin
      val execCommand = CommandUtils.orderCallLinuxScript(tmpDir, shBuffer = List(command))
      if (execCommand.isSuccess) {
        val outFile = new File(tmpDir, "out.txt")
        val outLines = Utils.file2Lines(outFile)
        val fileContents = outLines.drop(1).map { line =>
          List(outLines.head, line)
        }
        val missions = fileContents.map { newLines =>
          val resultData = tool.getResultData(newLines)
          val score = Shared.calculateScore(resultData)
          val result = Shared.getResult(score)
          val orinalData = tool.getOrinalData(newLines)
          MissionRow(
            id = 0,
            userId = userId,
            age = orinalData.age,
            ast = orinalData.ast,
            alt = orinalData.alt,
            plt = orinalData.plt,
            tyr = orinalData.tyr,
            tca = orinalData.tca,
            phe = orinalData.phe,
            trp = orinalData.trp,
            valine = orinalData.valine,
            ca = orinalData.ca,
            met = orinalData.met,
            gca = orinalData.gca,
            gcdca = orinalData.gcdca,
            tcdca = orinalData.tcdca,
            score = score.toString(),
            result = result,
            endTime = time
          )
        }
        val f = missionDao.insertAndRetunIds(missions.toList).flatMap { missionIds =>
          val etRows = missionIds.zip(fileContents).map { case (missionId, newLines) =>
            val missionIdDir = tool.getMissionIdDirById(missionId, userId)
            val outFile = new File(missionIdDir, "out.txt")
            newLines.toFile(outFile)
            val extraData = tool.getExtraData(outFile)
            val checkDate =
              if (StringUtils.isEmpty(extraData.checkDate)) Utils.dataTime2String(time)
              else extraData.checkDate
            val orinalData = tool.getOrinalData(newLines)
            val extraRow = tool
              .getEmptyExtraDataRow(missionId, userId)
              .copy(
                sampleId = orinalData.sampleId,
                name = extraData.name,
                unit = extraData.unit,
                address = extraData.address,
                sex = extraData.sex,
                office = extraData.office,
                doctor = extraData.doctor,
                number = extraData.number,
                sampleTime = extraData.sampleTime,
                submitTime = extraData.submitTime,
                sampleType = extraData.sampleType,
                sampleStatus = extraData.sampleStatus,
                title = extraData.title,
                reporter = extraData.reporter,
                checker = extraData.checker,
                checkDate = checkDate,
                reportDate = extraData.reportDate,
                testMethod = extraData.testMethod
              )
            val trashRow = TrashRow(missionId, userId, true)
            (extraRow, trashRow)
          }
          extraDataDao.insertOrUpdates(etRows).map { x =>
            missionIds
          }
        }
        val missionIds = Utils.execFuture(f)
        tool.deleteDirectory(tmpDir)
        missionDao
          .selectByMissionIds(missionIds)
          .zip(extraDataDao.selectByMissionIds(missionIds))
          .map { case (x, extraRows) =>
            val missionRows = x
            val extraMap = extraRows.map(x => (x.id, x)).toMap
            val t2s = missionRows
              .map(x => (x, extraMap.getOrElse(x.id, tool.getEmptyExtraDataRow(x.id, x.userId))))
            val array = Utils.getArrayByT2s(t2s)
            Ok(Json.obj("array" -> array, "valid" -> true))
          }
      } else {
        tool.deleteDirectory(tmpDir)
        Utils.result2Future(Ok(Json.obj("valid" -> false, "message" -> execCommand.getErrStr)))
      }
    } else {
      tool.deleteDirectory(tmpDir)
      Utils.result2Future(Ok(Json.obj("valid" -> myMessage.valid, "message" -> myMessage.message)))
    }

  }

  def downloadExampleFile = Action { implicit request =>
    val data = formTool.fileNameForm.bindFromRequest().get
    val file = new File(Utils.path, s"example/${data.fileName}")
    Ok.sendFile(file)
      .withHeaders(
        CACHE_CONTROL -> "max-age=3600",
        CONTENT_DISPOSITION -> s"attachment; filename=${file.getName}",
        CONTENT_TYPE -> "application/x-download"
      )
  }

  def exportExcel = Action { implicit request =>
    val data = formTool.basicInfoForm.bindFromRequest().get
    val file = new File(Utils.templateDir, s"live_forest_template.xlsx")
    val tmpDir = tool.createTempDirectory("tmpDir")
    val outFile = new File(tmpDir, "live_forest_out.xlsx")
    val is: InputStream = new FileInputStream(file)
    val os: OutputStream = new FileOutputStream(outFile)
    val context = new Context()
    context.putVar("basicInfo", data)
    JxlsHelper.getInstance().processTemplate(is, os, context)
    is.close()
    os.close()
    Ok.sendFile(
      outFile,
      onClose = () => {
        tool.deleteDirectory(tmpDir)
      }
    ).withHeaders(
      CONTENT_DISPOSITION -> s"attachment; filename=${outFile.getName}",
      CONTENT_TYPE -> "application/x-download"
    )
  }

  def export = Action.async { implicit request =>
    val data = formTool.basicInfoForm.bindFromRequest().get
    val pdfData = formTool.pdfDataForm.bindFromRequest().get
    val missionId = formTool.missionIdForm.bindFromRequest().get.missionId
    val userId = tool.getUserId
    val tmpDir = tool.createTempDirectory("tmpDir")
    val outPdfFile = new File(tmpDir, "live_forest_out.pdf")

    val marginTopStr = "0in"
    val pdf = if (Utils.isWindows) {
      Pdf(
        executablePath = s"${Utils.binPath}/wkhtmltopdf/bin/wkhtmltopdf.exe",
        new PdfConfig {
          orientation := Portrait
          pageSize := "A4"
          pageSize := "Letter"
          marginTop := marginTopStr
          marginBottom := "0in"
          marginLeft := "0.5in"
          marginRight := "0.5in"
        }
      )
    } else
      Pdf(new PdfConfig {
        orientation := Portrait
        pageSize := "A4"
        pageSize := "Letter"
        marginTop := "0in"
        marginBottom := "0in"
        marginLeft := "0.5in"
        marginRight := "0.5in"
      })
    missionDao.selectByMissionId(missionId).flatMap { row =>
      val missionIdDir = tool.getMissionIdDirById(missionId, userId)
      val outFile = new File(missionIdDir, "out.txt")
      val pngFile = new File(missionIdDir, "out.png")
      val svgFile = new File(missionIdDir, "out.svg")
      FileUtils.writeStringToFile(svgFile, pdfData.svgStr)
      Utils.svg2png(svgFile)
      val base64 = Utils.getBase64Str(pngFile)
      val logoFile = new File(Utils.dataDir, "hysw_logo.png")
      val logoBase64 = logoFile.base64
      val qrBase64 = new File(Utils.dataDir, "hysw_qr_code.jpg").base64
      val fontBase64 = new File(Utils.dataDir, "simsun.ttf").base64
      val imageInfo =
        Map("barPlot" -> base64, "logo" -> logoBase64, "qrCode" -> qrBase64, "font" -> fontBase64)
      val html = views.html.user.html(data, row, imageInfo, pdfData).toString()
      val htmlFile = new File(tmpDir, "out.html")
      html.toFile(htmlFile)
      pdf.run(htmlFile, outPdfFile)
      val bytes = FileUtils.readFileToByteArray(outPdfFile)
      val extraDataRow = ExtraDataRow(
        id = row.id,
        userId = row.userId,
        sampleId = data.sample,
        unit = data.unit,
        address = data.address,
        name = data.name,
        sex = data.sex,
        office = data.office,
        doctor = data.doctor,
        number = data.number,
        sampleTime = data.sampleTime,
        submitTime = data.submitTime,
        sampleType = data.sampleType,
        sampleStatus = data.sampleStatus,
        title = data.title,
        danger = data.danger,
        reporter = data.reporter,
        checker = data.checker,
        checkDate = data.checkDate,
        reportDate = data.reportDate,
        testMethod = data.testMethod
      )
      extraDataDao.insertOrUpdate(extraDataRow).map { x =>
        Ok(bytes).as("application/pdf")
      }

    }

  }

  def predictResult = Action.async { implicit request =>
    val data = Tool.transformRq.transform(reader[PredictResult.Request])
    missionDao
      .selectByMissionId(data.missionId)
      .zip(extraDataDao.selectByMissionId(data.missionId))
      .map { case (row, extraRow) =>
        val missionIdDir = tool.getMissionIdDirById(data.missionId, userId = data.userId)
        val outFile = new File(missionIdDir, "out.txt")
        val resultData = tool.getResultData(outFile)
        val json = ujson.Obj(
          "extra" -> writeJs(extraRow),
          "mission" -> writeJs(row),
          "result" -> writeJs(resultData)
        )
        Ok(json)
      }

  }

}
